Internet Control Message Protocol
1. 概述
就单纯的IP协议来说,本身并没有提供方法来发现哪些IP数据包发送往目的主机是出了问题的,除此之外,IP本身也无法获取当前机器到目的主机之间的诊断信息,这里的诊断信息指的是IP数据报经过了哪些路由器或是IP数据报与目的主机之间的往返耗时,为了解决以上问题,也就衍生出了Internet控制报文协议 (Internet Control Message Protocol - ICMP),将该协议用于提供与IP协议层配置和IP数据包处理相关的诊断信息
ICMP协议负责传递的是差错和控制报文,ICMP协议通常是IP层本身,上层的传输协议(如TCP / UDP等)又或者是应用程序本身触发的,需要注意的是,ICMP协议本身并不提供网络可靠性,它只会表明某些类别的故障或配置信息,仅此而已
ICMP的一个典型的应用场景就是平时使用的ping
工具,可以说ping
工具就是ICMP的实现,ping
用于测试主机之间的连通性,发送ICMP Echo Request消息并等待目标主机的Echo Reply响应,通过测量往返时间(Round-Trip Time,RTT),ping
可以评估网络的延迟
ICMPv4和ICMPv6分别指的是适用于IPv4的ICMP协议和适用于IPv6的ICMP协议
2. 报文格式
ICMP报文可以分为两大类:
- 有关IP数据传递的ICMP报文,也称为差错报文(error message)
- 有关信息采集和配置的ICMP报文,称为查询或信息类报文(information message)
其中ICMP报文的大小范围在MTU最大1500 Bytes - (2060 Bytes) = (14801440 Bytes),其中MTU占用大小范围如下所示:
同时根据以上可以得知ICMP报文段主要由以下几个部分构成:
- 类型(Type): 用于指定报文类型,大小为1 Byte,常见的有信息类报文的回显请求(Echo Request)和回显应答(Echo Reply),分别对应为类型
0x08
和类型0x00
,更多的ICMP报文类型可以参考以下:
- 代码(Code): 用于指定报文代码,用于更进一步地描述ICMP报文的内容,大小为 1 Byte,一般配合着类型字段进行解释,常见的类型与代码组合可以参考以下:
以上图源自《TCP/IP详解 卷1: 协议》- Page 250
- 检验和 (Checksum): 用于校验ICMP报文是否有损坏,占用2个字节
- 数据 (Data) : 填充的是ICMP的数据内容,可以是任意数据,其内容长度主要根据ICMP的报文类型和代码来决定,这些数据在ICMP请求中被发送,同时在ICMP响应中被返回,发送端可以通过比较发送和接收到的数据来检查网络的可达性和完整性,这里的数据没有实际是含义内容仅仅是用作填充ICMP报文中的数据字段,默认占用48个字节
3. ICMP超时
每个IPv4数据报在头部中都有一个生存周期(Time to Live,TTL)字段,而对应的IPv6数据报在其头部则是存在一个跳数限制(Hop Limit)字段,最初的设想是将这个8位的TTL字段保存为一个数据包被强制丢弃之前允许活跃在网络中的秒数(可以想象它是为了应为存在环状循环链路的情况),但实际情况下,路由器的转发速度远远快于一秒钟,因此该TTL字段的实际意义则变成了用于限定一个IPv4数据报在被路由器丢弃之前所允许的跳数限制
出现ICMP超时的情况有以下两种场景:
- 当TTL或跳数限制的字段值太小(为0或1)导致路由器将其报文进行丢弃时,会产生ICMP超时(代码为1)的报文
- 当分片的IP数据报只有部分到达目的地时(并不是所有的分片都到达),会产生ICMP超时报文用于告知发送者它的整个数据报备丢弃了,因为其中一个分片丢失,则意味着整个数据报丢失了
4. ICMP应用
a. traceroute
traceroute
是一个网络请求路径诊断工具,可以显示网络连接的实时路径,即数据包从源主机到目标主机之间经过的路由器,以及到达各个路由器的耗时,这个工具最早是在1988年由Van Jacobson
编写的,用于解决他遇到的网络问题
traceroute
的原理其实是通过发送一系列的Internet Control Message Protocol(ICMP) 回显请求,并控制IP报文中的生存时间(TTL)字段,该字段在每经过一台路由器时,会被路由器进行减1
,而如果当TTL字段减为0
,则路由器会丢弃该报文并直接返回ICMP超时到源主机,traceroute
则是利用这一机制,通过逐渐扩大IP报文中的TTL字段,来确定从源主机到目标主机过程中的所有路由器以及之间的访问耗时
在Linux系统中,这个工具为routetrace
,而在MS Windows中对应称之为tracert
,大多数情况下,可以直接通过routetrace hostname
或者tracert hostname
来追踪完整的请求路径
此外,traceroute
在不同操作系统下存在着不同实现方式,其实现方式包括了UDP、ICMP和TCP,在Linux和Cisco系统下,traceroute
默认使用的是UDP,使用UDP和ICMP均是采用相同的实现机制,即通过报文中的TTL字段来实现请求路径过程中的路由器探测,在Windows系统下,tracert
默认使用的是ICMP
除了以上两种类型的请求之外,traceroute
还支持以TCP报文进行请求路径勘测,其原理也是使用了IP数据报中的TTL
字段,使用TCP进行traceroute
的使用则会发送出一份SYN
数据包,而使用TCP协议来进行traceroute
最大的好处在于穿透防火墙的几率会更大,因为TCP SYN看起来就是一个正常的TCP连接请求数据包
Tips:
在IP网络中,不论是TCP / UDP / ICMP报文,都是封装在IP数据包中的,并且生存时间(TTL)字段也是存在于IP数据报中,当每经过一个路由器,生存时间字段则会减
1
,而当该生存时间字段值减为0
时,路由器则会丢弃该数据报,并统一向源主机发送ICMP超时消息(ICMP Time Exceeded Message)在IP数据报中设置生存时间(TTL)字段的目的在于防止IP数据报由于路由器出现配置异常导致循环情况的发生,永无止境地在互联网中进行传输
假设想要知道本地机器到达zchengb.top
主机的访问路径,则可以通过以下方式进行路径探测:
Tips:
- 以下测试环境基于MacOS进行,MacOS下,
traceroute
工具默认使用的是UDP,可以通过-I
参数调整为使用ICMP- 通过
-m 3
参数可以指定IP数据包中的TTL字段最大勘测值为3
,默认情况下,TTL的最大勘测值为64
1 | traceroute -I zchengb.top |
通过以上结果可以得知从本地机器访问zchengb.top
会经历14次路由器转发
其中第7/11/12/13次转发为*
,则表示对应无接收到ICMP回应报文,如果出现该情况,可以考虑为:
- 安全策略:防火墙拦截了相关的ICMP请求报文(为了防止网络攻击)
- 网络拥塞:当网络流量过大时,路由器可能会丢弃部分的ICMP数据报来进行拥塞缓解
- 路由器负载:当路由器负载过高时,可能会根据优先级丢弃低优先级的数据报,比如ICMP类型的数据报
- ICMP差错报文限制:为了防止一个差错报文引起另一个差错报文,导致死循环,ICMP协议规定了当某些情况下不会发送差错报文(如目的地址为广播地址或多播地址的数据报,已经是ICMP差错报文的数据报)
- 网络设备故障:由于设备故障导致无法处理ICMP报文
其中根据traceroute to zchengb.top (120.24.98.220), 64 hops max, 72 byte packets
可以得知,当前默认的最大路由转发次数为64
(即TTL=64),同时每一份ICMP数据报的总大小为72 Bytes
第一行则表示TTL=1时,所到达的路由器IP,同时在末端有三个时间耗时标识,表示测试了3趟往返,分别所消耗的时间为4.220 ms 3.879 ms 3.911 ms
以下是相关的Wireshark
抓包记录:
traceroute
显示的时间耗时是往返耗时,包含了数据包从源主机发送到中间节点,以及从中间节点返回到源主机的总时间,这个时间通常被称为往返时间(Round-Trip Time,RTT)
b. ping
Tips: 程序名
ping
源于声呐系统中定位物体,ping
程序是Mke Muuss于1983年12月编写的
ping
工具的工作原理就是基于ICMP查询报文,ping
工具是一种常用的网络诊断工具,通过发送ICMP Echo Request(回显请求)数据包到目标设备,如果目标设备可达并且ICMP功能开启,则对方会返回一个ICMP Echo Reply(回显应答)数据包
ping
工具会记录发送请求和接收应答的时间差,这个时间差就是所谓的往返时间(Round-Trip Time,RTT),因此,ping
工具可以用来检测网络设备的可达性和网络延迟
当ping
命令的实现中,会将ICMP报文的标识符字段设置为某个数值,发送主机能够利用它来分离返回的应答,在Linux系统中,发送进程的进程ID通常被作为标识符字段放置字ICMP报文中,如果有多个ping
在同一台主机同时运行的话,因为ICMP协议不像传输层协议那样有端口号,因此则是通过标识符字段来识别出对应的应答报文
ping
命令所发出和接收到的ICMP报文头部中存在着以下两个关键的字段:
- 标识符字段:UNIX操作系统下通常被设置为
ping
进程的进程ID,发送主机可以利用这个字段来识别返回的应答 - 序列号字段:当一个新
ping
运行时,序列号字段从0开始,并且每发送一个回显请求报文便会增加1,ping
会在输出中打印出每个返回的数据报的序列号,用于方便用户查看数据报是否丢失、重排或者重复
在下方的例子中,基于MacOS操作系统发送了一个ping
请求至当前局域网广播地址:
1 | ❯ ping 192.168.0.255 |
根据以上输出日志,可以观察到发出了4份ICMP报文,其中icmp_seq
序列号字段从0
依次递增至3
,同时由于发送目标为广播地址,因此每一份ICMP查询报文都对应的接收到了多份ICMP响应报文,具体的Wireshark抓包如下所示:
Tips:
ping
命令可通过-p
选项来添加自定义的数据内容,这些内容将被填充在ICMP报文中的data
字段中
根据以上报文,可以观察到每一个对应的ICMP回显请求都对应会有一个ICMP回显响应,ping
命令则是基于此往返耗时与响应内容来确定目的主机是否可以连通
同时也可以观察到ICMP请求报文中的标识符和序列号字段分别存在BE和LE两个值,其分别是表示Big Endian(大端序)和Little Endian(小端序)
- Big Endian: 在大端序中,最高有效字节存储在最低的内存地址,而最低有效字节存储在最高的内存地址,网络字节序通常采用的是大端序,这是因为大多数网络协议规定了字节序,以确保在不同体系结构之间的兼容性,Unix操作系统一般采用的是BE
- Little Endian: 在小端序中,最低有效字节(LSB)存储在最低的内存地址,而最高有效字节(MSB)存储在最高的内存地址。一些处理器体系结构,如x86架构,通常采用小端序,Windows操作系统一般采用的是LE
Wireshark主要考虑了两种不同操作系统(Windows为LE,Linux为BE)发出的ping
报文可能存在字节顺序不一样,因此将这两种进行了区分展示
打个比方,序列号(Sequence Number)在报文中展示为0000 0001
,对应十六进制为0x01
,在大端序(BE)的识别下,最左边为高位,因此该序列号的值为1
,而在小端序(LE)的识别下,最右边为高位,因此该序列号的值为256
根据以上,可以得知ping
命令就是利用ICMP协议中的Echo Request
和Echo Reply
等消息来进行网络诊断的
5. ICMP攻击
涉及ICMP攻击主要分为3类:泛洪攻击(flood)、炸弹(bomb)和信息泄露(information disclosure),其中的泛洪攻击会产生大量的流量,从而形成DoS攻击,炸弹攻击指的是发送经过改造的报文,导致目标设备处理报文时崩溃或者终止
- Smurf攻击:早期的ICMP攻击称之为Smurf攻击,相当于使用目的地址为广播地址的ICMPv4,同时对应的报文中源地址则被修改为了受害主机的地址,导致同一网段内大量计算机响应,这样做可以导致DoS攻击,因为受害主机只能忙于处理ICMP报文,对应的解决方案是在防火墙禁止传入的ICMP广播流量
- ICMP洪水攻击:攻击者发送大量伪造的ICMP Echo请求报文,导致被攻击设备的CPU需要响应这些报文,从而占用大量的带宽和CPU资源,使得设备无法提供正常的服务
- 死亡之Ping攻击:攻击者向目标设备发送超出目标设备处理能力范围内的巨大
ping
报文,从而导致设备崩溃或终止 - ICMP时间戳请求:攻击者发送大量的ICMP时间戳请求,导致被攻击的设备的CPU需要响应这些报文,从而占用大量的带宽和CPU资源,使得设备无法正常提供服务(和ICMP洪水攻击相似)